package processus;

import java.io.IOException;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map.Entry;
import java.util.Random;

public class Pere extends Thread {
    
    final static int CLOSE_VALUE = -1; // Valeur envoyée pour indiquer la fin de l'élection
    
    protected Integer nbPeresTotal;
    protected boolean launchExec = false;
    protected boolean stopExec = false;
    
    protected Integer pid = new Integer(0);
    protected Horloge horloge = new Horloge();
    protected FileDeRequetes fileDeRequetes;
    
    protected Tampon  tamponTrace = new Tampon(); 
    protected Tampon  tamponReception = new Tampon(); 
    protected LinkedHashMap<Integer, Tampon> listeTamponsEmissions   = new LinkedHashMap<Integer, Tampon>();
    
    protected HashMap<Integer, ClientEmission> listeClientsEmissions   = new HashMap<Integer, ClientEmission>();

    public Pere(Integer nbPeresTotal) {
        this.nbPeresTotal = nbPeresTotal;
    }
    
    @Override
    public void run() {
        
        // Lancement du client de trace (qui initialise le pid)
        ClientTrace cTrace = new ClientTrace(tamponTrace, this);
        cTrace.start();
        
        // Attente de l'initialisation du pid
        while(pid == 0) {
            try { Thread.sleep(100); } catch (InterruptedException ex) {}
        }
        
        // Lancement du client de reception
        ClientReception cReception = new ClientReception(tamponReception, pid);
        cReception.start();
        
        // Lancement des clients d'emission
        for(int pidDest = 1; pidDest <= nbPeresTotal; ++pidDest) {
            // Si ce n'est pas le pere courant
            if(pidDest != pid) {
                // Initialise le tampon
                Tampon tamponEmission = new Tampon();
                listeTamponsEmissions.put(pidDest, tamponEmission);
                
                // Initialise le client d'emission associé
                ClientEmission cEmission = new ClientEmission(tamponEmission, pid);
                listeClientsEmissions.put(pidDest, cEmission);
                cEmission.start();
            }
        }
        
        // Lancement de la file de requêtes de section critique
        fileDeRequetes = new FileDeRequetes(pid, nbPeresTotal);
        
        // --------------- Attente que tous les clients soient lancés ---------------
        while(launchExec == false) {
            try { Thread.sleep(100); } catch (InterruptedException ex) {}
        }
        
        System.out.println("[Pere P"+pid+"] Lancé");
        
        try { Thread.sleep(1000); } catch (InterruptedException ex) {}
        
        
        // --------------- DEBUT EXECUTION ---------------
        while(stopExec == false || !tamponReception.isEmpty()) {
            
            // On traite les nouveaux messages (non bloquant)
            traiterNouveauxMessages();
            
            // Si jamais on doit s'arréter et qu'il restait des messages à traiter
            if(stopExec) {
                break;
            }
            
            int action = new Random().nextInt(3); // alea dans [0, 3[
            // Action locale
            if(action == 0) {
                int time = horloge.actionLocale();
                tamponTrace.deposer("AL,"+pid+","+time);
            }
            // Si envoi d'un message vers un autre père
            else if(action == 1) {
                int indice = new Random().nextInt(listeTamponsEmissions.size()); // alea dans [0, nbTampons[
                ArrayList<Integer> listeKeys = new ArrayList<Integer>(listeTamponsEmissions.keySet()); // On récupère la liste des clés
                int pidDest = listeKeys.get(indice);
                sendMessage(pidDest, "MSG");
            }
            // Sinon envoi d'un message en diffusion
            else {
                int action2 = new Random().nextInt(2); // alea dans [0, 2[
                
                // Si section critique
                if(action2 == 0) {
                    // Diffusion de la requête
                    diffusionMessage("REQ");
                    
                    // Enregistrement dans la file
                    fileDeRequetes.requestSentOrReceived(pid, horloge.getTime());
                    
                    System.out.println("[Pere P"+pid+"] Demande d'entrée en section critique à "+horloge.getTime());
        
        
                    // On attend de pouvoir entrer en section critique
                    while(fileDeRequetes.canEnterSectionCritique() == false) {
                        // On traite les nouveaux messages (bloquant)
                        traiterNouveauxMessages();
                        try { Thread.sleep(100); } catch (InterruptedException ex) {}
                    }
                    
                    // ----- DEBUT SECTION CRITIQUE ----
                    int debutSCTime = horloge.actionLocale();
                    System.out.println("[Pere P"+pid+"] Début section critique à "+debutSCTime);
                    
                    int finSCTime = horloge.actionLocale();
                    int duree = new Random().nextInt(4); // alea dans [0, 4[
                    while(duree > 0) {
                        finSCTime = horloge.actionLocale();
                        duree--;
                    }
                    System.out.println("[Pere P"+pid+"] Fin section critique à "+finSCTime);
                    // ----- FIN SECTION CRITIQUE -----
                    
                    // On trace la section critique
                    String trace = "SC,"+pid+","+debutSCTime+","+finSCTime;
                    tamponTrace.deposer(trace);
                    
                    // On s'enlève de la file de section critique
                    fileDeRequetes.releaseSentOrReceived();
                    
                    // On prévient tout le monde
                    diffusionMessage("REAL");
                    
                }
                // Sinon message normal
                else {
                    diffusionMessage("MSG");
                }
            }
            
            try { Thread.sleep(1000); } catch (InterruptedException ex) {}
        
        }
        // --------------- FIN EXECUTION ---------------
        
        // On ordonne la fermeture de tous les fils
        closeFils();
        
        // On attend la fermeture des fils pour se fermer
        try {
            cTrace.join();
            cReception.join();
            // On attend tous les clients d'emission
            for (Entry<Integer, ClientEmission> clientEmission : listeClientsEmissions.entrySet()) {
                clientEmission.getValue().join();
            }
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
        
        System.out.println("[Pere P"+pid+"] Arrété");
    }

    /**
     * Traite les nouveaux messages entrants
     */
    public void traiterNouveauxMessages() {
        
        while(!tamponReception.isEmpty()) {
            
            String cmd = tamponReception.retirer(); // Forme cmd : 'pidSrc,pidDest,estampille,msg'
            
            String[] split = cmd.split(",");
            int pidSrc = Integer.parseInt(split[0]);
            int estampille = Integer.parseInt(split[2]);
            int currentTime = horloge.messageReceived(estampille);
            String msg = split[3]; // MSG | REQ | REP | REAL
            
            // On trace la réception du message
            String trace = msg+","+pidSrc+","+pid+","+estampille+","+currentTime;
            tamponTrace.deposer(trace);
            
            // Sinon on affiche le message reçu
            System.out.println("[Pere P"+pid+"] "+msg+" reçu (envoyé par P"+pidSrc+")");
            
            // Traitement des messages importants (de section critique)
            if(msg.equals("REQ")) {
                fileDeRequetes.requestSentOrReceived(pidSrc, estampille);
                sendMessage(pidSrc, "REP");
            }
            else if(msg.equals("REP")) {
                fileDeRequetes.replyReceived();
            }
            else if(msg.equals("REAL")) {
                fileDeRequetes.releaseSentOrReceived();
            }
        }
        
    }
    
    /**
     * Permet d'envoyer un message vers un autre père
     */
    public void sendMessage(int pidDest, String msg) {
        int estampille = horloge.messageSent();
        String cmd = pid+","+pidDest+","+estampille+","+msg;
        listeTamponsEmissions.get(pidDest).deposer(cmd);
    }
    
    /**
     * Permet d'envoyer un message vers tous les pères
     */
    public void diffusionMessage(String msg) {
        int estampille = horloge.messageSent();
        for (Entry<Integer, Tampon> entry : listeTamponsEmissions.entrySet()) {
            int pidDest = entry.getKey();
            String cmd = pid+","+pidDest+","+estampille+","+msg;
            listeTamponsEmissions.get(pidDest).deposer(cmd);
        }
    }
    
    /**
     * Appelé automatiquement par le fils ClientTrace
     */
    public synchronized void setPid(Integer pid) {
        this.pid = pid;
    }
    
    int getPid() {
        return this.pid;
    }
    
    
    /**
     * Ordonne la fermeture des fils
     */
    public void closeFils() {
        
        // Trace
        tamponTrace.deposer("CLOSE");
        
        // Reception
        try {
            Socket socket = new Socket(InetAddress.getLocalHost(), 6000+pid);
            PrintWriter out = new PrintWriter(socket.getOutputStream());
            out.println("CLOSE");
            out.flush();
            socket.close();
        }
        catch (IOException ex) {
            ex.printStackTrace();
        }
        
        // Emission
        for (Entry<Integer, Tampon> tamponEmission : listeTamponsEmissions.entrySet()) {
            tamponEmission.getValue().deposer("CLOSE");
        }
    }

    // Appelé par main pour lancer l'execution
    public synchronized void launchExec() {
        launchExec = true;
    }
    
    // Appelé par main pour lancer l'execution
    public synchronized void stopExec() {
        stopExec = true;
    }

    
}
